package eu.webhib.frmwrk.dao.jpa;

import eu.webhib.frmwrk.domain.intf.BaseEntity;
import eu.webhib.frmwrk.dao.intf.BaseDao;
import java.util.*;
import java.lang.reflect.*;
import javax.persistence.*;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.*;
import org.apache.log4j.Logger;

import eu.webhib.frmwrk.persistence.PersistenceMngr;

//
public class BaseDaoImpl<T extends BaseEntity> implements BaseDao<T> {
   //

   private static final Logger log = Logger.getLogger(BaseDaoImpl.class);
   //
   private Class<T> persistentClass;
   private static final String NAMED_ALL_QUERY_SUFFIX = "_All";

   /* This can only be used within a derived class of this class
    * this will only be done for application specific dao classes */
   public BaseDaoImpl() {
      persistentClass = (Class<T>) ((ParameterizedType) getClass().
              getGenericSuperclass()).getActualTypeArguments()[0];
   }

   public BaseDaoImpl(Class<T> persistentClass) {
      this.persistentClass = persistentClass;
   }

   @Override
   public T createNew() {
      try {
          T inst = persistentClass.newInstance();
          log.trace("createNew: " + inst);
          return inst;
      } catch (Exception ex) {
         log.fatal(ex);
      }
      return null;
   }

   @Override
   public void persist(T entity) {
      PersistenceMngr.getEntityManager().persist(entity);     // add a new object
   }

   @Override
   public T update(T entity) {
      return PersistenceMngr.getEntityManager().merge(entity);       // modify a detached object
   }

   @Override
   public void delete(T entity) {
      EntityManager em = PersistenceMngr.getEntityManager();
      log.trace("delete " + entity.getId());
      em.remove(em.getReference(persistentClass, entity.getId()));
   }

   @Override
   public void deleteAll() {
      for (T entity : findAll()) {
         log.trace("delete " + entity);
         delete(entity);
      }
   }

   @Override
   public T findById(Long id) {
      return PersistenceMngr.getEntityManager().find(persistentClass, id);
   }

   @Override
   public List<T> findByNamedQuerySuffix(String queryNameSuffix, Object... paramValues) {
      TypedQuery<T> query = getNamedQuery(queryNameSuffix);
      assert (query != null);
      int i = 0;
      for (Object value : paramValues) {
         query.setParameter(++i, value);
      }
      return query.getResultList();
   }

   @Override
   public List<T> findByAttribute(String attrName, Object attrValue) {
      EntityManager em = PersistenceMngr.getEntityManager();
      CriteriaQuery<T> criteria = em.getCriteriaBuilder().createQuery(persistentClass);

      EntityType<T> entity = em.getMetamodel().entity(persistentClass);
      SingularAttribute attr = entity.getSingularAttribute(attrName);

      Root<T> root = criteria.from(persistentClass);
      Predicate predicate = em.getCriteriaBuilder().equal(root.get(attr), attrValue);
      criteria.select(root).where(predicate);

      TypedQuery<T> query = em.createQuery(criteria);
      return query.getResultList();
   }

   @Override
   public List<T> findAll() {
      return createFindAllQuery().getResultList();
   }

   @Override
   public List<T> findAllRange(int startIndex, int size) {
      return createFindAllQuery().
              setFirstResult(startIndex).
              setMaxResults(size).
              getResultList();
   }

   @Override
   public int count() {
      EntityManager em = PersistenceMngr.getEntityManager();
      CriteriaQuery<Long> criteria = em.getCriteriaBuilder().createQuery(Long.class);
      Root<T> root = criteria.from(persistentClass);
      criteria.select(em.getCriteriaBuilder().count(root));
      TypedQuery<Long> query = em.createQuery(criteria);
      return query.getSingleResult().intValue();
   }

   private String checkForNamedQuery(String queryNameSuffix) {
      try {
         EntityManager em = PersistenceMngr.getEntityManager();
         String namedQueryString = persistentClass.getSimpleName() + queryNameSuffix;
         em.createNamedQuery(namedQueryString, persistentClass);
         log.info("checkForNamedQuery: " + namedQueryString + " found");
         return namedQueryString;
      } catch (IllegalArgumentException ex) {
         /** no such named query */
         return null;
      }
   }

   private TypedQuery<T> getNamedQuery(String queryNameSuffix) {
      try {
         EntityManager em = PersistenceMngr.getEntityManager();
         String namedQueryString = persistentClass.getSimpleName() + queryNameSuffix;
         TypedQuery<T> query = em.createNamedQuery(namedQueryString, persistentClass);
         log.info("getNamedQuery: " + namedQueryString + " found");
         return query;
      } catch (IllegalArgumentException ex) {
         /** no such named query */
         return null;
      }
   }

   private TypedQuery<T> createFindAllQuery() {
      TypedQuery<T> query = getNamedQuery(NAMED_ALL_QUERY_SUFFIX);
      if (query != null) {
         return query;
      }
      EntityManager em = PersistenceMngr.getEntityManager();
      CriteriaQuery<T> criteria = em.getCriteriaBuilder().createQuery(persistentClass);
      return em.createQuery(criteria.select(criteria.from(persistentClass)));
   }
}
